/*-----------------------------------------------------------------------------
	6502 Macroassembler and Simulator
		additions
Copyright (C) 2023 Mark Spankus

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-----------------------------------------------------------------------------*/

// Support for Ohio Scientific 65V monitor load, 65A monitor load and MOS checksum

#include "StdAfx.h"
#include "OSIFiles.h"
#include "resource.h"
#include <TCHAR.h>
#include "MarkArea.h"
#include "Sym6502.h"

#define BUF_SIZE 262144	//4x64K

/*
	The OSI 65V load format consist of the commands used to manipulate data in the 
	OSI 65V ROM monitor as used in video based OSI systems with polled keyboard.

	There are two modes, address and data.  
		Address mode is entered using the '.' character.
		Data mode is entered using the '/' character.
		The  'G' character transfers execution to the current address in the 
		address field.
		The 'L' character sets input from the ACIA instead of keyboard (no exit)

		When in address mode, hex characters are shifted in the address field from low
		nibble of low byte to high nibble of the high byte, until data mode or execute 
		command is received..

		When in Data mode, hex characters are shifted in from low nibble to high nibble, 
		written to the current address.
		When a <CR> is received the current address is incremented by one

		Only uppercase characters are accepted.
	Ex:
	.0400/78<CR>
	AD<CR>
	01<CR>
	FE<CR>
	F0<CR>
	0B<CR>
	A9<CR>
	00<CR>
	2C<CR>
	00<CR>
	DF<CR>
	30<CR>
	01<CR>
	2C<CR>
	A9<CR>
	80<CR>
	2C<CR>
	A9<CR>
	40<CR>
	.0400G	<JMP to $0400>
	*/								

void COSIFiles::Save65VFormat(CArchive &archive, COutputMem &mem, CMarkArea &area, int prog_start/*= -1*/)
{
	TCHAR buf[80], *ptr;

	for (UINT part=0; part<area.GetSize(); part++)
	{
		int start,end;

		area.GetPartition(part,start,end);
		ASSERT(start >= 0 && start <= 0xFFFF);
		ASSERT(end >= 0 && end <= 0xFFFF);
		ASSERT(start <= end);

		ptr = buf;
		//emit starting address
		ptr+=wsprintf(buf, _T(".%04X/"), start);
		archive.Write(buf,sizeof(TCHAR)*(ptr-buf));
		//emit data
		for (int i=start; i<=end; i++)
		{
			ptr = buf;
			ptr+=wsprintf(buf,_T("%02X\r"), mem[i] );
			archive.Write(buf,sizeof(TCHAR)*(ptr-buf));
		}
	}
	//emit GO address
	if (prog_start >= 0)
	{
		ptr = buf;
		ptr +=wsprintf(buf, _T(".%04XG"), prog_start);
		archive.Write(buf,sizeof(TCHAR)*(ptr-buf));	
	}		
 
}

void COSIFiles::Save65AFormat(CArchive &archive, COutputMem &mem, CMarkArea &area, int prog_start/*=-1*/)
{
	TCHAR buf[80], *ptr;

	for (UINT part=0; part<area.GetSize(); part++)
	{
		int start,end;

		area.GetPartition(part,start,end);
		ASSERT(start >= 0 && start <= 0xFFFF);
		ASSERT(end >= 0 && end <= 0xFFFF);
		ASSERT(start <= end);

		ptr = buf;
		//emit starting address
		ptr+=wsprintf(buf, _T("L%04X\r"), start);
		archive.Write(buf,sizeof(TCHAR)*(ptr-buf));
		int j = 0;

		//emit data
		for (int i=start; i<=end; i++)
		{
			ptr = buf;
			j++;
			ptr+=wsprintf(buf,_T("%02X"), mem[i] );

			//non HEX characters are ignored but make file easier to read for humans
			/*if (j < 8)
			{
				ptr+=wsprintf(buf,_T("%02X "), mem[i] );

			}
			else
			{
				ptr+=wsprintf(buf,_T("%02X\r"), mem[i] );
				j=0;
	
			}
			*/
			archive.Write(buf,sizeof(TCHAR)*(ptr-buf));
		}
		archive.Write(_T("R\0\0\0\0\0\0"),sizeof(TCHAR)*7);
	}
	//emit GO address
	// Upon G command, Serial monitor loads from the following
	//
	// $0129 - Y
	// $012A - X
	// $012B - A
	// $012C - Proc Status  'P'
	// $012D - stack pointer 'K'
	// $012E - PC Hi
	// $012F - PC Lo
	// When in Command Mode. 65A resets ACIA on every character except
	// L, P, & G
	//
	//the 'R' command caused the ACIA to be reset, along with reinitialization of the
	//65A monitor.  Some characters are lost from the data stream and serial syncronization 
	//may be compromised.  Anything other than P,G,L will also reset ACIA
			
	//since we cant pause output for a character count or two send a few NULS then write G

	if (prog_start >= 0)
	{
		ptr = buf;
		ptr +=wsprintf(buf, _T("L012900000004FD%02X%02XR"), ((prog_start >> 8) & 0xFF),(prog_start & 0xFF));
	
		archive.Write(buf,sizeof(TCHAR)*(ptr-buf));
		archive.Write(_T("\0\0\0\0\0\0G"), sizeof(TCHAR)*7);
	
	}
}

BOOL ishex( int c)
{
	return ( (c >= '0' && c <= '9') || (c>='A' && c<= 'F') );
}

BYTE dehex( int c)
{
    
    if (c >= _T('a') && c <= _T('f'))
        c -=32;
       
    if (c > _T('9'))
        c -= 7;
    c = c & 0x0F;
    return (BYTE) c;
    
}

//-----------------------------------------------------------------------------

void COSIFiles::Load65VFormat(CArchive &archive, COutputMem &mem, CMarkArea &area, int &prog_start)
{
	char buf[BUF_SIZE];
	UINT nSize = archive.Read( buf, BUF_SIZE);

	if (nSize)
	{
		char *p = (char*) buf;
		WORD addr = 0;
		BYTE data = 0;
		int nStart = -1;

		BOOL bAddrMode = TRUE;
		BOOL bFinished = FALSE;
		BOOL bInData = FALSE;

		while ( !bFinished && p < (char*) buf + nSize)
		{
			if (*p == '.')
				bAddrMode = TRUE;
			else if (*p == '/')
				bAddrMode = FALSE;
			else if (*p == 'G')
			{
				prog_start = addr;
				bFinished = TRUE;

			}
			else if ( isxdigit(*p))
			{
				if (bAddrMode)
				{
					addr = ((addr << 4) | dehex(*p)) & 0xFFFF;
	
					data = 0;//Peek(addr);

				}
				else
				{
					if (nStart < 0)
					{
						nStart = addr;
						area.SetStart(addr);
					}
					
					data = (data << 4) | dehex(*p);

					if (addr > 0xFFFF)	// za duży adres?
						throw (new COSIFilesException(COSIFilesException::E_FORMAT,row));
					mem[addr] = data;
				}

			}
			else if ( (*p == 0x0d || *p == 0x0a) && !bAddrMode)
			{
				area.SetEnd(addr);
				addr++;
			}
			p++;
		}

	}

}

void COSIFiles::Load65AFormat(CArchive &archive, COutputMem &mem, CMarkArea &area, int &prog_start)
{
	char buf[BUF_SIZE];
	UINT nSize = archive.Read( buf, BUF_SIZE);

	if (nSize)
	{
		char *p = (char*) buf;
		WORD addr = 0;
		BYTE data = 0;
		int nStart = -1;

		int nAddrMode = 0;
		int hi = 1;
		BOOL bFinished = FALSE;
		BOOL bAddrSet = 0;
		int nExec = 0;
		unsigned char  b = 0;

		while ( !bFinished && p < (char*) buf + nSize)
		{
			if (*p == 'L')	 //Load mode, expect 4 byte address
			{
				nAddrMode = 4;
				nStart = -1;
			}
			else if (*p == 'R')	 //Reset
			{
				nAddrMode = 0;
				hi = 1;
			}
			else if (*p == 0x20 || *p == 0x00)
			{
				nAddrMode = 0;
				hi = 1;
		
			}
			else if (*p == 'G')	  //Go command
			{
				if (bAddrSet > 1)
				{			
					prog_start = nExec;
				}
				bFinished = TRUE;
				while (p < (char*) buf + nSize && *p == 'G') //eat extras
					p++;
			}
			else if ( isxdigit(*p))
			{
				if (nAddrMode)
				{
					
					addr = (addr << 4) | dehex(*p);
					nAddrMode--;
					hi = 1;

					if (!nAddrMode)
					{
						if (nStart < 0)
						{
							nStart = addr;
							area.SetStart(addr);
						}
					}

				}
				else
				{
					
					if (addr > 0xFFFF)	// valid address?
						throw (new COSIFilesException(COSIFilesException::E_FORMAT,(p-buf)));

					if (hi)
					{
						b = (b & 0x0F) | (dehex(*p)<<4);
						mem[addr] = b;
					
						hi = 0;
					}
					else
					{
						b = (b & 0xF0) | dehex(*p);
						mem[addr] = b;
						if (addr == 0x012F || addr == 0x012E)
						{
							bAddrSet++;

							if (addr == 0x012F)
								nExec  |= b;
							else
								nExec |= (b << 8);

						}
						b=0;
						hi = 1;
						area.SetEnd(addr);
						addr++;

					}

				}

			}

			p++;
		}
	}

}

/*
The MOS Technology format 
The Lines
Each line consists of 5 fields. These are the length field, address field, data field, 
and the checksum. The lines always start with a semicolon (;) character.

The Fields
    ___ ________ _________ ______ __________ ______
   |_;_|_Length_|_Address_|_Data_|_Checksum_|_CRLF_|
	

Length
The record length field is a 2 character (1 byte) field that specifies the number of 
data bytes in the record. Typically this is 24 or less.
	

Address
This is a 2-byte address that specifies where the data in the record is to be loaded 
into memory, big-endian.
	

Data
The data field contains the executable code, memory-loadable data or descriptive 
information to be transferred.

Checksum
The checksum is an 2-byte field that represents the least significant two bytes of 
the the sum of the values represented by the pairs of characters making up the record’s 
length, address, and data fields, big-endian.

End of File	(optional?)
The final line should have a data length of zero, and the count of data lines in 
the address field. The checksum is not the usual checksum, it is instead a repeat 
of the data line count.

OSI specific
The final line consists of a $ character followed by 4 digit hex address to start execution
*/


void COSIFiles::SaveMOSChkFormat(CArchive &archive, COutputMem &mem, CMarkArea &area, int prog_start /*=-1*/)
{
  TCHAR buf[80], *ptr;
  int lines = 0;
  for (UINT part=0; part<area.GetSize(); part++)
  {
    int start,end;

    area.GetPartition(part,start,end);
    ASSERT(start >= 0 && start <= 0xFFFF);
    ASSERT(end >= 0 && end <= 0xFFFF);
    ASSERT(start <= end);

    const int STEP= 0x18;
    for (int i=start; i<=end; i+=STEP)
    {
      WORD sum= 0;				// checksum
      int lim= min(i+STEP-1, end);
	
      int cnt= i+STEP<=end ? STEP : end-i+1; //length of record
	  //   |_;_|_Length_|_Address_|_Data_|_Checksum_|_CRLF_|
      ptr = buf + wsprintf(buf,_T(";%02X%02X%02X"),cnt,(i>>8)&0xFF,i&0xFF);
      sum += cnt + ((i>>8)&0xFF) + (i&0xFF);
      for (int j=i; j<=lim; j++)
      {
        ptr += wsprintf(ptr,_T("%02X"),mem[j]);
		sum += mem[j];
      }		
      ptr += wsprintf(ptr,_T("%02X%02X\r"),(sum>>8)&0xFF, sum & 0xFF);
      archive.Write(buf,sizeof(TCHAR)*(ptr-buf));
	  lines++;
    }
  }
  if (lines)
  {
    ptr = buf + wsprintf(buf,_T(";00%02X%02X%02X%02X\r"),(lines>>8)&0xFF,lines&0xFF,(lines>>8)&0xFF,lines&0xFF);
	archive.Write(buf,sizeof(TCHAR)*(ptr-buf));

  }
  if (prog_start >=0)
  {
    ASSERT(prog_start >= 0 && prog_start <= 0xFFFF);
    int sum= -( ((prog_start>>8)&0xFF) + (prog_start&0xFF) + 1 ) & 0xFF;
    ptr = buf + wsprintf(buf,_T("$%02X%02X"),(prog_start>>8)&0xFF,prog_start&0xFF);
	archive.Write(buf,sizeof(TCHAR)*(ptr-buf));
  }

}
UINT COSIFiles::ReadHex8(const TCHAR *&ptr, WORD &sum)	
{
  UINT res= 0;
  for (int i=0; i<2; i++)
  {
    res <<= 4;
	const TCHAR c = *ptr++;
	if (c >= _T('0') && c <= _T('9'))
	{
		res += (c - _T('0'));
	}
	else
	{
		
		if (c >= _T('A') && c<= _T('F'))
		{
			res += 10 + (c - _T('A'));
		}
		else if (c >= _T('a') && c<= _T('f'))
		{
			res += 10 + (c - _T('a'));
		}
		else
		{
			throw COSIFilesException(COSIFilesException::E_FORMAT,row);
		}

	}
  }
  sum += res;	
  return res;
}


void COSIFiles::LoadMOSChkFormat(CArchive &archive, COutputMem &mem, CMarkArea &area, int &prog_start)
{
	TCHAR buf[256];
	UINT i;
	WORD sum;

	for (row=1; ; row++)
	{
		if (!archive.ReadString(buf,sizeof buf))
			break;

		if (_tcsclen(buf) == sizeof(buf)-1)
			COSIFilesException(COSIFilesException::E_FORMAT,row);		// overflow

		const TCHAR *ptr= buf;

		if (*ptr == _T(';'))
		{			
			ptr++;
			//   |_;_|_Length_|_Address_|_Data_|_Checksum_|_CRLF_|
			WORD sum= 0;		
			UINT cnt= ReadHex8(ptr,sum);	// #bytes expected
			UINT addr= ReadHex8(ptr,sum);
			addr <<= 8;
			addr += ReadHex8(ptr,sum);	// address

			if (cnt)
				area.SetStart(addr);
			//read row data
			for (i=0; i<cnt; i++)
			{
				if (addr > 0xFFFF)
					throw COSIFilesException(COSIFilesException::E_FORMAT,row);	// za duży adres
				mem[addr++] = (UINT8)ReadHex8(ptr,sum);
			}
			area.SetEnd(addr);
			WORD junk;
			WORD chk = (ReadHex8(ptr,junk)<<8) || ReadHex8(ptr,junk);
			if (chk != sum)
			{
				throw COSIFilesException(COSIFilesException::E_CHKSUM,row);

			}
		}
		else if (*ptr == _T('$'))
		{
			ptr++;
			UINT addr= ReadHex8(ptr,sum);
			addr <<= 8;
			addr += ReadHex8(ptr,sum);	// start
			prog_start = addr;
		}
		else 
		{
			if (_istspace(*ptr))
			{
				ptr++;
			}
			else
			{
				COSIFilesException(COSIFilesException::E_FORMAT,row);		// overflow

			}
		}

	}
}


//-----------------------------------------------------------------------------

/*virtual*/ BOOL COSIFiles::COSIFilesException::GetErrorMessage(LPTSTR lpszError,
  UINT nMaxError, PUINT pnHelpContext/*= NULL*/)
{
  CString msg;
  TCHAR num[16];
  if (pnHelpContext != NULL)
    *pnHelpContext = 0;
  wsprintf(num,_T("%u"),row);
  switch (error)
  {
    case E_BAD_FORMAT:		// błędny format pliku
      msg.LoadString(IDS_INTEL_HEX_ERR_2);
      break;
    case E_CHKSUM:		// błąd sumy kontrolnej
      AfxFormatString1(msg,IDS_INTEL_HEX_ERR_1,num);
      break;
    case E_FORMAT:		// błędny format danych
      AfxFormatString1(msg,IDS_INTEL_HEX_ERR_3,num);
      break;
    default:
      return FALSE;
  }
  _tcsnccpy(lpszError,msg,nMaxError);
  return TRUE;
}

//QSave is a binary version of MOS Checksum format
//
void COSIFiles::LoadQSaveFormat(CArchive &archive, COutputMem &mem, CMarkArea &area, int &prog_start)
{
	unsigned char buf[BUF_SIZE];
	UINT nSize = archive.Read( buf, BUF_SIZE);

	if (!nSize)
	{
		throw COSIFilesException(COSIFilesException::E_BAD_FORMAT,0);
		return;
	}
	unsigned char *p = (unsigned char*) buf;
	unsigned char *end = buf+nSize;
	WORD addr = 0;
	BYTE data = 0;
	int row = 0;
	int nStart = -1;
	bool bFinished = false;
		
	while (!bFinished && p < end)
	{
		if (*p == ';' && end-p > 7)
		{
			row++;
			unsigned int wSum = 0;

			int nCount = *(p+1);
			addr = (*(p+2) << 8 ) | *(p+3);
			
			if (nStart < 0)
			{
				nStart = addr;
				area.SetStart(addr);
			}

			wSum += *( p+1);
			wSum += *( p+2);
			wSum += *( p+3);

			p +=4;

			while (nCount-- && p < end)
			{
				mem[addr++] = *p;
				wSum += *p++;	
			}
			area.SetEnd(addr);
			WORD wChk = (*(p) << 8) | *(p+1);
			p +=2;
			wSum &= 0xFFFF;
			
			if (wChk != wSum)
			{
				throw COSIFilesException(COSIFilesException::E_CHKSUM,row);
			}
			continue;  
		}
		if (*p == '$')
		{
			int go =  *(p+2) | ( *(p+1) << 8);
			prog_start = go;
			p+=3;
			bFinished = TRUE;

		}
		p++;
	}
		
}

void COSIFiles::SaveQSaveFormat(CArchive &archive, COutputMem &mem, CMarkArea &area, int prog_start /*=-1*/)
{
  unsigned char buf[80], *ptr;
  int lines = 0;
  for (UINT part=0; part<area.GetSize(); part++)
  {
    int start,end;

    area.GetPartition(part,start,end);
    ASSERT(start >= 0 && start <= 0xFFFF);
    ASSERT(end >= 0 && end <= 0xFFFF);
    ASSERT(start <= end);

    const int STEP= 0x18;
    for (int i=start; i<=end; i+=STEP)
    {
      WORD sum= 0;				// checksum
      int lim= min(i+STEP-1, end);
	
      int cnt= i+STEP<=end ? STEP : end-i+1; //length of record
	  //   |_;_|_Length_|_Address_|_Data_|_Checksum_|_CR_|
      ptr = buf;
      *(ptr++) = ';';
      *(ptr++) = (unsigned char) cnt;
      *(ptr++) = (unsigned char)(i>>8)&0xFF;
      *(ptr++) = (unsigned char)i&0xFF;
      
      sum += cnt + ((i>>8)&0xFF) + (i&0xFF);
      for (int j=i; j<=lim; j++)
      {
        *(ptr++) = mem[j];
		sum += mem[j];
      }
      *(ptr++) = (unsigned char) (sum>>8)&0xFF;		
      *(ptr++) = (unsigned char) sum & 0xFF;		
      *(ptr++) = (unsigned char) 0x0d;		
      archive.Write(buf,sizeof(TCHAR)*(ptr-buf));
	  lines++;
    }
  }

  if (prog_start >=0)
  {
    ASSERT(prog_start <= 0xFFFF);
    ptr = buf;
	*(ptr++) = (unsigned char) '$';
	*(ptr++) = (unsigned char)(prog_start>>8)&0xFF;
	*(ptr++) = (unsigned char)prog_start&0xFF;
	archive.Write(buf,sizeof(TCHAR)*(ptr-buf));
  }

}
